package deepr.lib;

import deepr.anno.LifeCycle;
import deepr.anno.Type;
import deepr.core.*;
import deepr.entity.DeeprRequest;
import deepr.exception.LifeException;
import deepr.filter.FilterParser;
import deepr.src.graph.GraphDisposeContext;
import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.Map;


@Slf4j
public class GraphQlLib {

  public static void main2(String[] args) throws NoSuchMethodException {
//    SnowIdHandler.class.getMethod("select",
//        HandlerContext.class, String.class, String.class, Object.class);
  }

  //    public static String
  //%{
  //  "device" => %{
  //    "()" => %{},
  //    "=>" => ["id", "name", "model", "status", "imgs"],
  //    "[]" => 1,
  //    "match" => %{},
  //    "optional" => %{"director" => ["id", "name"], "room" => ["id", "name"]},
  //    "total_page" => true
  //  }
  //}
  public static String list(GraphDisposeContext ctx, DeeprRequest deeprRequest) throws Exception {
    String object = deeprRequest.getObject();
    Object[] filter = deeprRequest.getFilter();
    FilterParser filterParser = new FilterParser(ctx);
    StringBuilder filterStr = filterParser.parse(object, filter);
    String[] select = deeprRequest.getSelect();
    HashMap<String, String[]> optional = deeprRequest.getOptional();
    HashMap<String, String[]> match = deeprRequest.getMatch();
    Integer page = deeprRequest.getPage();
    SchemaData schemaData = SchemaManager.getSchemaData(object);
    StringBuilder matchStr = new StringBuilder();
    StringBuilder withStr = new StringBuilder();
    StringBuilder withObjStr = new StringBuilder();
    HashMap<String, String[]> p = new HashMap<>();
    p.put(object, select);
    getMatch(ctx, object, p, schemaData, matchStr, withStr);
    getOptional(ctx, object, optional, schemaData, matchStr, withStr);
    StringBuilder retObj = new StringBuilder();
    retObj.append(object).append(":").append(object);
    withObjStr.append(object);
    match.forEach((key, value) -> {
      retObj.append(",").append(key).append(":").append(key);
      withObjStr.append(",").append(key);
    });
    optional.forEach((key, value) -> {
      retObj.append(",").append(key).append(":").append(key);
      withObjStr.append(",").append(key);
    });
    ctx.setOptional("");
    getMatch(ctx, object, match, schemaData, matchStr, withStr);
    // order by xx where xx
    matchStr.append("WITH ").append(withObjStr).append("\n").
        append("WITH ").append(withStr).
        append(filterStr).
        append("WITH COLLECT({").append(retObj).append("}) as list\n").
        append(getPageStr(object, page, 20));
    return matchStr.toString();
  }


  public static String info(GraphDisposeContext ctx, DeeprRequest deeprRequest) throws Exception {
    String object = deeprRequest.getObject();
    HashMap<String, Object>[] filter = deeprRequest.getFilter();
    FilterParser filterParser = new FilterParser(ctx);
    StringBuilder filterStr = filterParser.parse(object, filter);
    String[] select = deeprRequest.getSelect();
    HashMap<String, String[]> optional = deeprRequest.getOptional();
    HashMap<String, String[]> match = deeprRequest.getMatch();
    SchemaData schemaData = SchemaManager.getSchemaData(object);

    StringBuilder matchStr = new StringBuilder();
    StringBuilder withStr = new StringBuilder();
    StringBuilder withObjStr = new StringBuilder();
    HashMap<String, String[]> p = new HashMap<>();
    p.put(object, select);
    getMatch(ctx, object, p, schemaData, matchStr, withStr);
    getOptional(ctx, object, optional, schemaData, matchStr, withStr);
    StringBuilder retObj = new StringBuilder();
    retObj.append(object).append(":").append(object);
    withObjStr.append(object);
    match.forEach((key, value) -> {
      retObj.append(",").append(key).append(":").append(key);
      withObjStr.append(",").append(key);
    });
    optional.forEach((key, value) -> {
      retObj.append(",").append(key).append(":").append(key);
      withObjStr.append(",").append(key);
    });
    ctx.setOptional("");
    getMatch(ctx, object, match, schemaData, matchStr, withStr);
    // order by xx where xx
    matchStr.append("WITH ").append(withObjStr).append("\n").
        append("WITH ").append(withStr).
        append(filterStr).
        append("RETURN \"").append(object).append("\" AS object,").append(retObj).append(" AS one limit 1");
    return matchStr.toString();
  }


  //  %{"director" => ["id", "name"], "room" => ["id", "name"]}
  //  MATCH (staff:EXAMPLE_USER) -[access:ACCESS]-> (example_project:EXAMPLE_PROJECT{id: "crsw"})
  //  OPTIONAL MATCH (example_user)-[:GOT]->(access_tag:EXAMPLE_ACCESS_TAG)
  //  OPTIONAL MATCH (example_user)-[:DUTY]->(lab:EXAMPLE_LAB)
  //  WITH access,staff,lab,access_tag where $access_roles IN access.roles
  //  WITH access,staff,
  //  CASE WHEN NOT lab IS NULL THEN COLLECT({id: lab.id, name: lab.name}) ELSE [] END AS lab,
  //  CASE WHEN NOT access_tag IS NULL THEN {id: access_tag.id, name: access_tag.name} ELSE NULL END AS access_tag
  //  RETURN "staff" AS object, 1 as page, COLLECT({id: staff.id, name: staff.name, phone: staff.phone, lab: lab, access_tag: access_tag})[0..19] AS list,
  //  (COUNT(access) + 20 - 1) / 20 AS total_page

  //  {class deepr.entity.User={schema=user, phone={type=STRING},
  //  subject={schema=class deepr.annotation.DeeprEnum, label=, dir=->, type=JsonArray},
  //  prefix=true, name={type=STRING}, dbType=NEO4J,
  //  belongTo={schema=class deepr.entity.Project, label=ACCESS, dir=IN, object=access},
  //  id={type=STRING, primaryKey=true}, label=USER,
  //  device={schema=class deepr.entity.Device, label=DIRECT, dir=->, type=OneToMany}, primaryKey=NULL},
  //  class deepr.entity.Project={schema=project, prefix=false, name={type=STRING}, dbType=NEO4J,
  //  id={type=STRING, primaryKey=true}, label=PROJECT, primaryKey=NULL},
  //  class deepr.entity.Device={schema=device, prefix=false, name={type=STRING}, dbType=NEO4J, id={type=STRING, primaryKey=true}, label=DEVICE, primaryKey=NULL}}
  public static void getMatch(GraphDisposeContext ctx, String targetObj, HashMap<String, String[]> match,
                              SchemaData schemaData, StringBuilder matchStr, StringBuilder withStr) throws Exception {
    for (Map.Entry<String, String[]> ent : match.entrySet()) {
      String object = ent.getKey();
      boolean is_self = object.equals(targetObj);
      String[] select = match.get(object);
      HashMap<String, PropertyData> objectFields;
      PropertyData relInfo = null;
      if (is_self)
        objectFields = schemaData.getFields();
      else {
        relInfo = schemaData.getFields().getOrDefault(object, null);
        log.debug("match object:{}, relInfo: {}", object, relInfo);
        if (relInfo == null) {
          continue;
        }
        objectFields = SchemaManager.getSchemaData(relInfo.getSchema()).getFields();
      }
      if (objectFields == null)
        throw new LifeException(object + ", field is null", LifeCycle.PARSING, object);
      String kvStr = FilterLib.parseSelectKeys(ctx, objectFields, object, select);
      if (ctx.appearContains(object)) {
        if (withStr.length() > 0)
          withStr.append(",");
        withStr.append("CASE WHEN NOT ").append(object).append(" IS NULL THEN {").append(kvStr).append("} ELSE NULL END AS ").append(object).append("\n");
        continue;
      }
      log.info("targetObj: {}, object: {}, select:{}", targetObj, object, select);
      ctx.addAppear(object);
      if (!is_self) {
        SchemaData relObjSchemaData = SchemaManager.getSchemaData(relInfo.getSchema());

        Type type = relInfo.getType();
        Type relDir = relInfo.getPoint();
        SchemaData realSchemaData = SchemaManager.getSchemaData(relInfo.getRelation());
        String relLabel = realSchemaData.getName();
        String lowerLabel = realSchemaData.getObject();
        if (withStr.length() > 0)
          withStr.append(",");
        matchStr.append(ctx.getOptional()).append("MATCH (").append(targetObj).append(")").append(getDir(lowerLabel, relLabel, relDir)).append("(").append(object).append(":").append(relObjSchemaData.getName()).append(")\n");
        if (type == Type.OneToMany) {
          withStr.append("CASE WHEN NOT ").append(object).append(" IS NULL THEN COLLECT({").append(kvStr).append("}) ELSE [] END AS ").append(object).append("\n");
        } else if (type == Type.OneToOne) {
          withStr.append("CASE WHEN NOT ").append(object).append(" IS NULL THEN {").append(kvStr).append("} ELSE NULL END AS ").append(object).append("\n");
        }
      } else {
        matchStr.append(ctx.getOptional()).append("MATCH (").append(targetObj).append(":").append(schemaData.getName()).append(")\n");
        withStr.append("{").append(kvStr).append("} AS ").append(object).append("\n");
      }
    }
  }

  public static StringBuilder getForeignMatch(GraphDisposeContext ctx, String object, PropertyData propertyData) {
    StringBuilder sb = new StringBuilder();
    log.info("object: {}, foreignObject: {}", object, propertyData);
    String foreignObject = propertyData.getName();
    if (ctx.foreignExists(foreignObject)) {
      return sb;
    }
    SchemaData foreignSchema = SchemaManager.getSchemaData(propertyData.getSchema());
    String foreignLabel = foreignSchema.getName();
    Type point = propertyData.getPoint();
    String relationLabel = propertyData.getRelationLabel();
    String relationObject = propertyData.getRelationObject();
    return sb.append("MATCH (").append(object).append(")").
        append(getDir(relationObject, relationLabel, point)).
        append("(").append(foreignObject).append(":").append(foreignLabel).append(")\n");
  }

  public static void getOptional(GraphDisposeContext ctx, String targetObj, HashMap<String, String[]> match,
                                 SchemaData schemaData, StringBuilder matchStr, StringBuilder withStr) throws Exception {
    if (match.size() > 0) {
      ctx.setOptional("OPTIONAL ");
      getMatch(ctx, targetObj, match, schemaData, matchStr, withStr);
    }
  }

  public static String getDir(String obj, String schema, Type dir) {
    String str = "[" + obj + ":" + schema + "]";
    if (dir == Type.IN)
      return "-" + str + "->";
    return "<-" + str + "-";
  }

  public static String getPageStr(String object, int page, int pageSize) {
    return "RETURN \"" + object + "\" AS object, " + page + " AS page, " + "(SIZE(list) + " + (pageSize - 1) + ")/" + pageSize + " AS total_page, SIZE(list) as total_count, " +
        "list[" + (page - 1) + ".." + pageSize * page + "] as list\n";
  }

  public static String getInfoStr(String object, int page, int pageSize) {
    return "RETURN \"" + object + "\" AS object, " + page + " AS page, " + "(SIZE(list) + " + (pageSize - 1) + ")/" + pageSize + " AS total_page, SIZE(list) as total_count, " +
        "list[" + (page - 1) + ".." + pageSize * page + "] as list\n";
  }


  public static String getTotalPageStr(String object, int pageSize) {
    return "(SIZE(list) + " + (pageSize - 1) + " / " + pageSize + ") AS total_page, SIZE(list) as total_count\n";
  }

}
